package cn.skyquant.quant4j.jforex.strategy;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.economics.EconomicsCategoryImpactDTO;
import cn.skyquant.quant4j.api.economics.EconomicsDTO;
import cn.skyquant.quant4j.api.economics.EconomicsService;
import cn.skyquant.quant4j.api.economics.EconomicsServiceProxy;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseStrategy;
import cn.skyquant.quant4j.jforex.sdk.strategy.InstrumentEntity;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 奥丁
 * 基于财经消息的EA
 */
@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Odin extends BaseStrategy implements IBarFeedListener {

    @Configurable("交易货币")
    public Instrument i1 = Instrument.EURUSD;

    @Configurable("K柱大于几个点做单")
    public int k = 5;

    @Configurable("几个点加仓")
    public int point = 5;

    @Configurable("加几仓")
    public int p = 15;

    EconomicsService economicsService;

    @Override
    protected String getVersion() {
        return "011";
    }

    @Override
    protected String getName() {
        return "Odin";
    }

    @Override
    protected String getEaName() {
        return "Odin";
    }

    @Override
    protected void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    protected void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    @Override
    protected void onAccountEx(IAccount account) {
    }

    @Override
    protected void init(InitEntity initEntity) {
        context.subscribeToBarsFeed(i1, Period.DAILY, OfferSide.ASK, this);//日线
        context.subscribeToBarsFeed(i1, period, OfferSide.ASK, this);//小K线
        insMap.put(i1, new InstrumentEntity(i1));
        economicsService = new EconomicsServiceProxy(account.getAccountId(), BorkerType.DUKASCOPY);
        IChart chart = context.getChart(i1);
        if (chart != null) {
            IFeedDescriptor feedDescriptor = chart.getFeedDescriptor();
            if (feedDescriptor != null) {
                feedDescriptor.setPeriod(Period.FIVE_MINS);
                feedDescriptor.setOfferSide(OfferSide.ASK);
                chart.setFeedDescriptor(feedDescriptor);
                chart.repaint();
            }
        }
        initEntity.r = true;
    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        if (period == Period.DAILY) {
            dailyOpen(instrument, bar.getTime());
        } else if (period == this.period) {
            try {
                process(instrument, bar);
            } catch (JFException e) {
                err("error:%s", e.getMessage());
            }
        }
    }

    //每10秒钟运行的
    private void process(Instrument instrument, IBar bar) throws JFException {
        String symbolP = instrument.getPrimaryJFCurrency().getSymbol();
        String symbolS = instrument.getSecondaryJFCurrency().getSymbol();
        //这个bar的time会提前10秒
        List<EconomicsDTO> economicsListP = economicsMap.get(symbolP);
        if (economicsListP != null && economicsListP.size() > 0) {
            processByType(instrument, bar, economicsListP, symbolP, TradeDirection.LONG);
        }
        List<EconomicsDTO> economicsListS = economicsMap.get(symbolS);
        if (economicsListP != null && economicsListS.size() > 0) {
            processByType(instrument, bar, economicsListS, symbolP, TradeDirection.LONG);
        }
    }

    //入场条件2:根据10秒钟的K线来决定做单方向
    private void processByType(Instrument instrument, IBar bar, List<EconomicsDTO> economicsList, String symbol, TradeDirection td) throws JFException {
        InstrumentEntity ie = insMap.get(instrument);
        long time = bar.getTime();
        ITick tick = history.getLastTick(instrument);
        double k_size = Math.abs(bar.getClose()-bar.getOpen());
        for (EconomicsDTO economics : economicsList) {
            if (time == economics.publicTimestamp) {
//                out("symbol=%s,id=%d,time_gmt=%s,time_cst=%s,impacts=%s",
//                        symbol,
//                        economics.id,
//                        CalendarUtils.formatGMTStandard(economics.publicTimestamp),
//                        CalendarUtils.formatStandard(economics.publicTimestamp),
//                        economics.impacts);
                if ((bar.getClose() - bar.getOpen()) > ie.pipValue*k) {
                    for(int i=0;i<p;i++){
                        String label = instrument.name() + "_"+i+"_" + CalendarUtils.formatGMTStandard(bar.getTime()) + "_SHORT_" + economics.id;
                        double currentPrice = tick.getAsk();
                        double sl = new BigDecimal(currentPrice + (p+1)*ie.pipValue*point).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
                        double tp = new BigDecimal(bar.getLow()).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
                        double open = new BigDecimal(currentPrice+ie.pipValue*point*i).setScale(ie.scale+1,RoundingMode.UP).doubleValue();
                        engine.submitOrder(label, instrument, IEngine.OrderCommand.SELLLIMIT, ie.minAmount, open, 0, sl, tp);
                    }
                }
                if(bar.getOpen()  - bar.getClose() > ie.pipValue*k){
                    for(int i=0;i<p;i++){
                        String label = instrument.name() + "_"+i+"_" + CalendarUtils.formatGMTStandard(bar.getTime()) + "_LONE_" + economics.id;
                        double currentPrice = tick.getAsk();
                        double sl = new BigDecimal(currentPrice - (p+1)*ie.pipValue*point).setScale(ie.scale + 1, RoundingMode.DOWN).doubleValue();
                        double tp = new BigDecimal(bar.getHigh()).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
                        double open = new BigDecimal(currentPrice - ie.pipValue*point*i).setScale(ie.scale+1,RoundingMode.DOWN).doubleValue();
                        engine.submitOrder(label, instrument, IEngine.OrderCommand.BUYLIMIT, ie.minAmount, open, 0, sl, tp);
                    }
                }
            }
        }
    }

    @Override
    protected void onMessageEx(IMessage message) {
        if (message.getOrder() == null) {
            return;
        }
        IOrder order = message.getOrder();
        IMessage.Type type = message.getType();
        String label = order.getLabel();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        double amount = order.getAmount();
        double open = order.getOpenPrice();
        double tp = order.getTakeProfitPrice();
        double sl = order.getStopLossPrice();
        double close = order.getClosePrice();
        double profit = order.getProfitLossInUSD();
        switch (type) {
            case ORDER_FILL_OK:
                notif("Fill %s %s,amount=%s,open=%s,sl=%s,tp=%s", orderCommand.name(), label, amount, open, sl, tp);
                break;
            case ORDER_CLOSE_OK:
                try {
                    for(IOrder orderlimit:engine.getOrders()){
                        if(orderlimit.getOrderCommand().isConditional()){
                            orderlimit.close();
                        }
                    }
                } catch (JFException e) {
                    err("cancel order:e");
                }
                if (profit > 0) {
                    info("Close %s %s,amount=%s,open=%s,sl=%s,tp=%s,close=%s,profit=%s", orderCommand.name(), label, amount, open, sl, tp, close, profit);
                } else if(profit<0) {
                    err("Close %s %s,amount=%s,open=%s,sl=%s,tp=%s,close=%s,profit=%s", orderCommand.name(), label, amount, open, sl, tp, close, profit);
                }
                break;
        }
    }


    final Map<Instrument, Double> atrMap = new HashMap<>();//大K柱ATR
    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体


    private long dailytime = 0;

    //开盘时要干的事儿
    private void dailyOpen(Instrument instrument, long time) {
        calcATR(instrument, time);//计算波动率
        calcMessage();//计算消息
    }

    List<EconomicsDTO> empty = new ArrayList<>();
    List<EconomicsDTO> economicsList = empty;
    Map<String, List<EconomicsDTO>> economicsMap = new HashMap<>();

    private void calcMessage() {
        long currenttime = context.getTime();
        if (currenttime - dailytime < Period.DAILY.getInterval()) {
            return;
        }
        dailytime = currenttime;
        economicsMap.clear();
        ResultDTO<List<EconomicsDTO>> resultDTO = economicsService.findBetweenPublicTime(context.getTime(), context.getTime() + Period.DAILY.getInterval());

        if (resultDTO.ok) {
            economicsList = resultDTO.data;
            for (EconomicsDTO economicsDTO : economicsList) {
                if (economicsDTO.impacts.isEmpty()) {
                    continue;
                }
                Set<EconomicsCategoryImpactDTO> impactSet = economicsDTO.impacts;
                for (EconomicsCategoryImpactDTO impact : impactSet) {
                    String symbol = impact.symbol;
                    List<EconomicsDTO> economicsList = economicsMap.get(symbol);
                    if (economicsList == null) {
                        economicsList = new ArrayList<>();
                        economicsMap.put(symbol, economicsList);
                    }
                    economicsList.add(economicsDTO);
                }
            }
        } else {
            economicsList = empty;
        }
    }

    //计算日波动吕
    private void calcATR(Instrument instrument, long time) {
        atrMap.remove(instrument);
        InstrumentEntity ie = insMap.get(instrument);
        double[] atrs = null;
        try {
            atrs = indicators.atr(instrument, Period.DAILY, OfferSide.BID, 22, Filter.WEEKENDS, 1, time, 0);
        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
        }
        if (atrs != null && atrs.length > 0 && atrs[0] != 0) {
            atrMap.put(instrument, atrs[0]);
        }
    }
}
